package com.yunsom.testdemo.multiple_thread.concurrent_tools;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

public class ConcurrentHashMapTest {

  public static  void main(String[] arg) {
    //ConcurrentHashMap 是java集合中map的实现，是哈希表的线程安全版本，即使是线程安全版本
    Map<String,Integer> count=new ConcurrentHashMap<>();
    //CountDownLatch其实可以把它看作一个计数器，只不过这个计数器的操作是原子操作，同时只能有一个线程去操作这个计数器，
    // 也就是同时只能有一个线程去减这个计数器里面的值
    CountDownLatch endLatch = new CountDownLatch(3);

    Runnable runnable=new Runnable() {
      @Override
      public void run() {
        for(int i=0;i<5;i++)
        {
          //
          Integer value = count.get("123");
          if (null == value) {
            // 添加键值对
            count.put("123", 1);
          } else {
            count.put("123", value + 1);
          }
        }
        //countDown 的时候每次调用都会对 state 减 1 也就是我们
        // new CountDownLatch(3); 的这个计数器的数字减 1
        endLatch.countDown();
      }
    };

    new Thread(runnable).start();
    new Thread(runnable).start();
    new Thread(runnable).start();

    try {
      //此方法用来让当前线程阻塞，直到count减小为0才恢复执行,await 方法它会去获取同步值发现为
      // 0 的话成功返回，如果小于 0 的话，再次判断是否是头结点
      endLatch.await();
      System.out.println(count);
    } catch (Exception e) {
      e.printStackTrace();
    }

    /*
    1. ConcurrentHashMap中变量使用final和volatile修饰有什么用呢？
      Final域使得确保初始化安全性（initialization safety）成为可能，初始化安全性让不可变形对象不需要同步就能自由地被访问和共享。
      使用volatile来保证某个变量内存的改变对其他线程即时可见，在配合CAS可以实现不加锁对并发操作的支持。get操作可以无锁是由于Node
      的元素val和指针next是用volatile修饰的，在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。
    2.我们可以使用CocurrentHashMap来代替Hashtable吗？
      我们知道Hashtable是synchronized的，但是ConcurrentHashMap同步性能更好，因为它仅仅根据同步级别对map的一部分进行上锁。
      ConcurrentHashMap当然可以代替HashTable，但是HashTable提供更强的线程安全性。它们都可以用于多线程的环境，但是当Hashtable
      的大小增加到一定的时候，性能会急剧下降，因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation)，
      不论它变得多么大，仅仅需要锁定map的某个部分，而其它的线程不需要等到迭代完成才能访问map。简而言之，在迭代的过程中，
      ConcurrentHashMap仅仅锁定map的某个部分，而Hashtable则会锁定整个map。
    3. ConcurrentHashMap有什么缺陷吗？
      ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据，但不会把整个表都锁住。同步读取操作则是完全非阻塞的。
      好处是在保证合理的同步前提下，效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据，
      期间线程B调用get，则只能get到目前为止已经顺利插入的部分数据。
    4. ConcurrentHashMap在JDK 7和8之间的区别
      JDK1.8的实现降低锁的粒度，JDK1.7版本锁的粒度是基于Segment的，包含多个HashEntry，而JDK1.8锁的粒度就是HashEntry（首节点）
      JDK1.8版本的数据结构变得更加简单，使得操作也更加清晰流畅，因为已经使用synchronized来进行同步，所以不需要分段锁的概念，
      也就不需要Segment这种数据结构了，由于粒度的降低，实现的复杂度也增加了
      JDK1.8使用红黑树来优化链表，基于长度很长的链表的遍历是一个很漫长的过程，而红黑树的遍历效率是很快的，代替一定阈值的链表，
      这样形成一个最佳拍档

    总结
    其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap，相对而言，ConcurrentHashMap只是增加了同步的操作来控制并发，
    从JDK1.7版本的ReentrantLock+Segment+HashEntry，到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。
    1.数据结构：取消了Segment分段锁的数据结构，取而代之的是数组+链表+红黑树的结构。
    2.保证线程安全机制：JDK1.7采用segment的分段锁机制实现线程安全，其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
    3.锁的粒度：原来是对需要进行数据操作的Segment加锁，现调整为对每个数组元素加锁（Node）。
    4.链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时，会将链表转化为红黑树进行存储。
    5.查询时间复杂度：从原来的遍历链表O(n)，变成遍历红黑树O(logN)。
     */

  }
}